package handlers

import (
	"fmt"
	"net/http"

	"v8s-sm-proxy/internal"
	"v8s-sm-proxy/internal/dropapp"
	gen_consolegw "v8s-sm-proxy/rest/target/generated-sources/consolegw"
	gen_devprofile "v8s-sm-proxy/rest/target/generated-sources/devprofile"
	"v8s/logger"

	"github.com/labstack/echo/v4"
	"go.uber.org/zap"
)

type (
	handlers struct {
		log     *zap.SugaredLogger
		manager *dropapp.Manager
	}
)

const (
	suspendedProperty = "suspend"
	composeProperty   = "compose_yml"

	unmarshallErrorCode = "FAILED_TO_UNMARSHAL_REQUEST"
)

func NewHandlers(manager *dropapp.Manager) *handlers {
	result := &handlers{
		log:     logger.Logger().Named("http_server").Sugar(),
		manager: manager,
	}

	return result
}

func (s *handlers) CreateResource(ctx echo.Context) error {
	request := &gen_devprofile.CreateResourceJSONRequestBody{}

	if err := ctx.Bind(request); err != nil {
		return ctx.JSON(http.StatusBadRequest, gen_devprofile.ErrorDto{
			Code:    unmarshallErrorCode,
			Message: err.Error(),
		})
	}

	rn, err := internal.ParseResourceNameFromDevelopersProfileAPI(request.Name)
	if err != nil {
		return ctx.JSON(http.StatusBadRequest, gen_devprofile.ErrorDto{
			Code:    unmarshallErrorCode,
			Message: fmt.Sprintf("failed to parse resource name '%s': %s", request.Name, err),
		})
	}

	properties := *request.Properties
	compose, ok := properties[composeProperty].(string)
	if !ok {
		s.log.Errorf("'%s' is not present, or is not string in request '%+v'", composeProperty, request)

		return ctx.JSON(http.StatusBadRequest, gen_devprofile.ErrorDto{
			Code:    unmarshallErrorCode,
			Message: fmt.Sprintf("'%s' is not present, or is not string", composeProperty),
		})
	}

	result, err := s.manager.Create(ctx.Request().Context(), compose, rn.AccountID, rn)
	if err != nil {
		return ctx.JSON(http.StatusInternalServerError, gen_devprofile.ErrorDto{
			Code:    "FAILED_TO_CREATE_TENANT",
			Message: err.Error(),
		})
	}

	status := string(result.Status)

	return ctx.JSON(http.StatusCreated, gen_devprofile.ResourceDto{
		Name: result.RN.ToDeveloperProfilesAPI(),
		State: gen_devprofile.ResourceStateDto{
			Value: status,
		},
	})
}

func (s *handlers) DeleteResourceById(ctx echo.Context, name gen_devprofile.ResourceName) error {
	rn, err := internal.ParseResourceNameFromDevelopersProfileAPI(name)
	if err != nil {
		return ctx.JSON(http.StatusBadRequest, gen_devprofile.ErrorDto{
			Code:    unmarshallErrorCode,
			Message: fmt.Sprintf("failed to parse resource name '%s': %s", name, err),
		})
	}

	if err := s.manager.Delete(ctx.Request().Context(), rn); err != nil {
		return ctx.JSON(http.StatusInternalServerError, gen_devprofile.ErrorDto{
			Code:    "FAILED_TO_DELETE_RESOURCE",
			Message: err.Error(),
		})
	}

	return ctx.NoContent(http.StatusNoContent)
}

func (s *handlers) GetResourceById(ctx echo.Context, name gen_devprofile.ResourceName) error {
	rn, err := internal.ParseResourceNameFromDevelopersProfileAPI(name)
	if err != nil {
		return ctx.JSON(http.StatusBadRequest, gen_devprofile.ErrorDto{
			Code:    unmarshallErrorCode,
			Message: fmt.Sprintf("failed to parse resource name '%s': %s", name, err),
		})
	}

	task, err := s.manager.GetTask(ctx.Request().Context(), rn)
	if err != nil {
		return ctx.JSON(http.StatusInternalServerError, gen_devprofile.ErrorDto{
			Code:    "FAILED_TO_GET_INFORMATION",
			Message: err.Error(),
		})
	}

	return ctx.JSON(http.StatusOK, convert(task))
}

func (s *handlers) UpdateResourceById(ctx echo.Context, name gen_devprofile.ResourceName) error {
	rn, err := internal.ParseResourceNameFromDevelopersProfileAPI(name)
	if err != nil {
		return ctx.JSON(http.StatusBadRequest, gen_devprofile.ErrorDto{
			Code:    unmarshallErrorCode,
			Message: fmt.Sprintf("failed to parse resource name '%s': %s", name, err),
		})
	}

	request := &gen_devprofile.ResourceUpdateDto{}

	if err := ctx.Bind(request); err != nil {
		return ctx.JSON(http.StatusBadRequest, gen_devprofile.ErrorDto{
			Code:    unmarshallErrorCode,
			Message: err.Error(),
		})
	}

	properties := request.Properties
	suspended, ok := properties[suspendedProperty].(bool)
	if !ok {
		return ctx.JSON(http.StatusBadRequest, gen_devprofile.ErrorDto{
			Code:    unmarshallErrorCode,
			Message: fmt.Sprintf("request do not contains '%s' property", suspendedProperty),
		})
	}

	if suspended {
		err = s.manager.Suspend(ctx.Request().Context(), rn)
	} else {
		err = s.manager.Unsuspend(ctx.Request().Context(), rn)
	}

	if err != nil {
		return ctx.JSON(http.StatusInternalServerError, gen_devprofile.ErrorDto{
			Code:    "FAILED_TO_UPDATE_RESOURCE",
			Message: err.Error(),
		})
	}

	task, err := s.manager.GetTask(ctx.Request().Context(), rn)
	if err != nil {
		return ctx.JSON(http.StatusInternalServerError, gen_devprofile.ErrorDto{
			Code:    "FAILED_TO_OBTAIN_INFORMATION",
			Message: err.Error(),
		})
	}

	return ctx.JSON(http.StatusOK, convert(task))
}

func (s *handlers) GetComposeSchema(ctx echo.Context) error {

	compose, err := s.manager.GetCompose(ctx.Request().Context())
	if err != nil {
		return ctx.JSON(http.StatusInternalServerError, gen_devprofile.ErrorDto{
			Code:    "FAILED_TO_GET_COMPOSE",
			Message: err.Error(),
		})
	}
	return ctx.JSON(http.StatusOK, gen_consolegw.DropAppComposeSchema(compose))

}

func (s *handlers) SearchResources(ctx echo.Context) error {
	request := &gen_devprofile.SearchRequestDto{}
	if err := ctx.Bind(request); err != nil {
		return ctx.JSON(http.StatusBadRequest, gen_devprofile.ErrorDto{
			Code:    unmarshallErrorCode,
			Message: err.Error(),
		})
	}

	if request.UpdatedFrom == nil {
		return ctx.JSON(http.StatusBadRequest, gen_devprofile.ErrorDto{
			Code:    unmarshallErrorCode,
			Message: "updated_from is required",
		})
	}

	if request.Limit == nil {
		return ctx.JSON(http.StatusBadRequest, gen_devprofile.ErrorDto{
			Code:    unmarshallErrorCode,
			Message: "limit is required",
		})
	}

	resources, err := s.manager.SearchResourcesUpdatedAfter(ctx.Request().Context(), *request.UpdatedFrom, *request.Limit)
	if err != nil {
		return ctx.JSON(http.StatusInternalServerError, gen_devprofile.ErrorDto{
			Code:    "FAILED_TO_SEARCH_RESOURCES",
			Message: err.Error(),
		})
	}

	items := make([]gen_devprofile.ResourceDto, 0, len(resources))
	for _, resource := range resources {
		current := resource
		items = append(items, convert(current))
	}

	size := len(items)

	response := gen_devprofile.SearchResponseDto{
		Items: &items,
		Size:  &size,
	}

	return ctx.JSON(http.StatusAccepted, response)
}

func convert(task dropapp.Task) gen_devprofile.ResourceDto {
	endpoints := make([]gen_devprofile.EndpointDto, 0, len(task.Endpoints))

	for _, endpoint := range task.Endpoints {
		endpointUrl := endpoint.URL
		endpointType := string(endpoint.Type)
		endpointDescription := endpoint.Description

		endpoints = append(endpoints, gen_devprofile.EndpointDto{
			Url:         &endpointUrl,
			Type:        &endpointType,
			Description: &endpointDescription,
		})
	}

	state := string(task.Status)

	return gen_devprofile.ResourceDto{
		Name:      task.RN.ToDeveloperProfilesAPI(),
		Endpoints: &endpoints,
		State: gen_devprofile.ResourceStateDto{
			Value: state,
		},
	}
}
